<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>2FA TOTP 生成器</title>
  <style>
    /* 重置与基础样式 */
    * {
      box-sizing: border-box;
      margin: 0;
      padding: 0;
    }
    body {
      font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
      background: #f4f4f4;
      color: #333;
      line-height: 1.6;
      padding: 10px;
    }
    /* 居中容器 */
    .container {
      max-width: 500px;
      margin: 20px auto;
      background: #fff;
      border-radius: 8px;
      padding: 20px;
      box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
    }
    h1 {
      text-align: center;
      font-size: 1.8em;
      margin-bottom: 20px;
    }
    /* 表单与按钮样式 */
    input[type="text"] {
      width: 100%;
      padding: 10px;
      font-size: 1em;
      border: 1px solid #ccc;
      border-radius: 4px;
      margin-bottom: 10px;
    }
    button {
      width: 100%;
      padding: 10px;
      font-size: 1em;
      background: #007bff;
      color: #fff;
      border: none;
      border-radius: 4px;
      cursor: pointer;
      transition: background 0.3s ease;
    }
    button:hover {
      background: #0056b3;
    }
    .result {
      margin-top: 20px;
      text-align: center;
    }
    .result p {
      margin: 10px 0;
      font-size: 1.2em;
    }
    #qrcode {
      margin: 20px auto;
      text-align: center;
    }
    /* 移动端适配 */
    @media (max-width: 500px) {
      .container {
        margin: 10px;
        padding: 15px;
      }
      h1 {
        font-size: 1.5em;
      }
      input[type="text"], button {
        font-size: 0.9em;
      }
    }
  </style>
</head>
<body>
  <div class="container">
  <h1>2FA TOTP 生成器</h1>
  <div id="app">
    <input type="text" id="secret" placeholder="请输入密钥（Base32编码）" />
    <button id="generate">生成验证码</button>
    <div id="result">
      <p>验证码：<span id="code"></span></p>
      <p>剩余时间：<span id="time"></span> 秒</p>
    </div>
    <div id="qrcode"></div>
  </div>
  </div>
  <!-- 引入二维码生成库 -->
    <script src="{{ url_for('static', filename='js/qrcode.min.js') }}"></script>
  <script>
    // Base32 解码函数，将 Base32 字符串转换为 Uint8Array
    function base32Decode(input) {
      const base32chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
      // 去除尾部的“=”补位，并统一转为大写
      input = input.toUpperCase().replace(/=+$/, '');
      let bits = "";
      for (let i = 0; i < input.length; i++) {
        const val = base32chars.indexOf(input[i]);
        if (val === -1) {
          throw new Error("输入中包含无效的Base32字符：" + input[i]);
        }
        bits += val.toString(2).padStart(5, '0');
      }
      const bytes = [];
      for (let i = 0; i + 8 <= bits.length; i += 8) {
        bytes.push(parseInt(bits.substr(i, 8), 2));
      }
      return new Uint8Array(bytes);
    }

    // 生成 TOTP 验证码函数，采用 HMAC-SHA1 算法
    async function generateTOTP(secret) {
      // 将 Base32 密钥解码为字节数组
      const keyBytes = base32Decode(secret);

      const epoch = Math.floor(Date.now() / 1000);
      const timeStep = 30; // 时间窗口：30秒
      let counter = Math.floor(epoch / timeStep);

      // 将计数器转换成8字节的数组（大端格式）
      let counterBytes = new Uint8Array(8);
      for (let i = 7; i >= 0; i--) {
        counterBytes[i] = counter & 0xff;
        counter = counter >> 8;
      }

      // 使用 Web Crypto API 导入密钥（HMAC-SHA1）
      const cryptoKey = await crypto.subtle.importKey(
        "raw",
        keyBytes,
        { name: "HMAC", hash: { name: "SHA-1" } },
        false,
        ["sign"]
      );

      // 对计数器进行签名，获取 HMAC 值
      const signature = await crypto.subtle.sign("HMAC", cryptoKey, counterBytes);
      const hmac = new Uint8Array(signature);

      // 动态截断处理（根据 RFC 4226）
      const offset = hmac[hmac.length - 1] & 0xf;
      const binary =
        ((hmac[offset] & 0x7f) << 24) |
        ((hmac[offset + 1] & 0xff) << 16) |
        ((hmac[offset + 2] & 0xff) << 8) |
        (hmac[offset + 3] & 0xff);

      const otp = binary % 1000000; // 取余生成6位验证码
      const code = otp.toString().padStart(6, '0');

      // 计算当前时间窗口内剩余秒数
      const remaining = timeStep - (epoch % timeStep);

      return { code: code, remaining: remaining };
    }

    // 更新验证码和剩余时间的显示
    async function updateDisplay(secret) {
      try {
        const result = await generateTOTP(secret);
        document.getElementById("code").innerText = result.code;
        document.getElementById("time").innerText = result.remaining;
      } catch (e) {
        console.error(e);
        alert("生成验证码时出错：" + e.message);
      }
    }

    // 生成二维码，将密钥转换为 otpauth 协议链接
    function updateQRCode(secret) {
      const qrDiv = document.getElementById("qrcode");
      // 清空之前的二维码
      qrDiv.innerHTML = "";
      // 构建 otpauth 协议的 URL，可以根据需要修改 label 与 issuer 参数
      let otpAuthUrl = `otpauth://totp/My2FA?secret=${encodeURIComponent(secret)}&issuer=MyApp`;
      // 生成二维码，设置宽高为128像素
      new QRCode(qrDiv, {
        text: otpAuthUrl,
        width: 128,
        height: 128
      });
    }

    // UI 模式：点击按钮后获取用户输入的密钥，并更新验证码与二维码显示
    document.getElementById("generate").addEventListener("click", () => {
      const secret = document.getElementById("secret").value.trim();
      if (secret === "") {
        alert("请输入密钥！");
        return;
      }
      // 生成二维码
      updateQRCode(secret);
      // 立即更新验证码与倒计时显示
      updateDisplay(secret);
      // 如果之前设置过定时器，则先清除
      if (window.totpInterval) clearInterval(window.totpInterval);
      window.totpInterval = setInterval(() => updateDisplay(secret), 1000);
    });

    // API 模式：若 URL 中含 secret 参数，则直接返回 JSON 数据（不显示二维码等 UI）
    (async () => {
      const urlParams = new URLSearchParams(window.location.search);
      if (urlParams.has("secret")) {
        const secret = urlParams.get("secret");
        try {
          const result = await generateTOTP(secret);
          document.body.innerHTML = "";
          document.write(JSON.stringify(result));
        } catch (e) {
          document.body.innerHTML = "生成验证码时出错：" + e.message;
        }
      }
    })();
  </script>
</body>
</html>
